package com.jml.jdk8.stream;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;

/**
 * @author:jml
 * @date: 2019/12/18 0018 21:38
 * @description：
 */
public class StreamTest {


    public static void main(String[] args) {

        /**
         *流转化为数组
         */
        Stream<String> stream = Stream.of("hello", "world", "helloworld");
        //给定一个整型，返回一个值
        //  <A> A[] toArray(IntFunction<A[]> generator);
        //因为toArray这个返回就是A[]数组
        // 因为这个stream里面是String，所以返回数组也就是String类型的
        String[] stringArray = stream.toArray(a -> new String[a]);
        Arrays.asList(stringArray).forEach(System.out::println);
        System.out.println("1----------------------------------------------------------------------------");

        /**
         *流转化为数组，使用方法引用修改
         */
        Stream<String> stream1 = Stream.of("hello", "world", "helloworld");
        String[] strings = stream1.toArray(String[]::new);
        Arrays.asList(strings).forEach(System.out::println);
        System.out.println("2----------------------------------------------------------------------------");

        /**
         * 流转化为list,简单使用
         */
        Stream<String> stream2 = Stream.of("hello", "world", "helloworld");
       List<String> stringList = stream2.collect(Collectors.toList());
//       String reduce = stream2.reduce((s, b) -> s + b).toString();
        //Set<String> collect3 = stream2.collect(Collectors.toSet());
        System.out.println(stringList);
        System.out.println("3----------------------------------------------------------------------------");

        /**
         * 流转化为list，原始写法，简单使用的剖析
         */
        Stream<String> stream3 = Stream.of("hello", "world", "helloworld");
        /*<R> R collect(
          Supplier<R> supplier, --不接受参数返回一个值，最后这里返回一个list------得到新的list
          BiConsumer<R, ? super T> accumulator, --接受2个参数不返回值，一个参数是上一个的list一个参数是stream中的值----把stream中的值放入新的list中,这个就是实际的操作
          BiConsumer<R, R> combiner  --接受2个参数不返回值，一个参数是一个中list------里面可以写任何的东西
          );
        theList就是第一个参数创建的ArrayList，
        item:就是Stream中的每一个元素
        theList2和theList1：就是将上一步得到的list放入一个新的list中(thelist2和thelist1不分先后顺序)
        */
        List<String> collect1 =  stream3.collect(
                ()-> new ArrayList(), //Supplier<R> supplier,
                (theList,item) -> theList.add(item), //BiConsumer<R, ? super T> accumulator,
                (theList1,theList2) -> theList2.removeAll(theList1) //BiConsumer<R, R> combiner
                );
        System.out.println(collect1);
        System.out.println("4----------------------------------------------------------------------------");
        //转化为方法引用
        Stream<String> stream4 = Stream.of("hello", "world", "helloworld");
        List<String> collect2 =  stream4.collect(
                ArrayList::new, //Supplier<R> supplier,
                ArrayList::add,//BiConsumer<R, ? super T> accumulator,
                ArrayList::addAll//BiConsumer<R, R> combiner
        );
        System.out.println(collect2);
        System.out.println("5----------------------------------------------------------------------------");


        /**
         * 转化为字符串
         */
        Stream<String> stream5 = Stream.of("hello", "world", "helloworld");
        String s = stream5.collect(() -> new StringBuilder(),
                (s3,item) -> s3.append(item),
                (s1,s2) -> s2.toString()
                ).toString();
        System.out.println(s);
        /**
         * 转化为字符串--方法引用
         */
        Stream<String> stream7 = Stream.of("hello", "world", "helloworld");
        String s7 = stream7.collect(StringBuilder::new,
                StringBuilder::append,
                (s1,s2) -> s2.toString()
                ).toString();
        System.out.println(s7);
        /**
         * 转化为字符串，并且转化为大写字母
         */
        Stream<String> stream6 = Stream.of("hello", "world", "helloworld");
        String s1 = stream6.collect(() -> new StringBuilder(),
                (s3,item) -> s3.append(item.toUpperCase()),
                (s0,s2) -> s2.toString()
        ).toString();
        System.out.println(s1);
        System.out.println("6----------------------------------------------------------------------------");

        /**
         * 不使用Collectors.toList()，使用Collectors.toCollection()
         * Collectors.toCollection()这个可以自定义返回类型，默认是ArrayList,也可以自定义
         * Collectors.toList()的返回类型是写死的
         */
        Stream<String> stream8 = Stream.of("hello", "world", "helloworld","hello");
        List<String> list = stream8.collect(Collectors.toCollection(ArrayList::new));
        System.out.println(list);
        Stream<String> stream9 = Stream.of("hello", "world", "helloworld","hello");
        Set<String> list1 = stream9.collect(Collectors.toCollection(HashSet::new));
        System.out.println(list1);
        System.out.println("7----------------------------------------------------------------------------");

        /**
         * 使用Collectors.joining()生成字符串
         * 把元素拼接为字符串，按照遇到的顺序
         */
        Stream<String> stream10 = Stream.of("hello", "world", "helloworld","hello");
        String collect = stream10.collect(Collectors.joining());
        System.out.println(collect);
        System.out.println("8----------------------------------------------------------------------------");
    }


    /**
     * 对集合中的每个元素进行操作
     * 将集合中的每个元素转化为大写
     * 将集合中的每个元素转化为大写这是一种元素的映射，元素映射用map，map里面的参数是Function接口，就是给定一个值返回另一个值,
     * 先用map转换，然后再用collect把流转化为list返回。
     */
    @Test
    public void 对集合中每个元素进行操作用map(){
        List<String> list = Arrays.asList("hello", "world", "helloworld");
        List<String> collectList1 = list.stream().map(str -> str.toUpperCase()).collect(Collectors.toList());
        System.out.println(collectList1);
        //不使用方法引用
        List<String> collectList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
        System.out.println(collectList);
    }

    /**
     *Function<? super T, ? extends Stream<? extends R>> mapper
     * flatMap参数也是Function，输入是任何值，输出是继承了Stream的值
     * Stream中的每个元素都是不同的ArrayList，把这些多个list转化为一个大的List同时对所有的list的元素进行操作
     */
    @Test
    public void flatMap(){
        //进行平方，然后合成一个大的List
        Stream<List<Integer>> stream = Stream.of(Arrays.asList(1),Arrays.asList(1,2),Arrays.asList(1,2,3));
        //flatMap(theList -> theList.stream()) 获取流里面的list然后根据list获取stream，
        //map(item -> item * item)把所有的值进行平方操作
        List<Integer> listAll = stream
                .flatMap(theList -> theList.stream())
                .map(item -> item * item)
                .collect(Collectors.toList());
        System.out.println(listAll);
    }

    /**
     * Stream有of方法获取流
     * 有empty方法获取一个空流
     * 有concat方法将两个流拼接到一块获取一个新的流
     * 有generate方法,参数是Supplier,不接受参数返回值
     * 有iterate方法
     */
    @Test
    public void stream的generate方法(){
        Stream<String> stream1 = Stream.generate(()->UUID.randomUUID().toString());
        //函数引用
        Stream<String> stream = Stream.generate(UUID.randomUUID()::toString);
        //findFirst就是找到第一个对象，不对后面进行处理
        Optional<String> first = stream.findFirst();
        //Optional的正确使用需要先进性判断为空的操作
        first.ifPresent((a) -> System.out.println(first.get()));
        first.ifPresent(System.out::println);
    }

    /**
     * final T seed, final UnaryOperator<T> f  ,UnaryOperator  extends Function<T, T>
     *返回一个无限的串行的有序的stream
     */
    @Test
    public void stream的iterate方法(){
        //初始值为1，后面的每个元素都是前一个元素+2,如果不加条件就会成为一个无线流，无穷多个数
        //所以需要加限制条件limit来限制产生多少个。
        Stream.iterate(1, item -> item + 2).limit(6).forEach(item -> System.out.println(item));
    }

    @Test
    public void iterate的注意事项(){
        //这行代码会打印出0 1 但是这个程序一直都不会停下来，永远执行下去
        //distinct不知道iterate只会输出0和1，所以就一直在帮iterate进行去重
        Stream.iterate(0 , i -> (i+1)%2 ).distinct().limit(6).forEach(System.out::println);
        //这样就不会一直运行下去了iterate的输出是固定的值，后面的函数都不会等待
        Stream.iterate(0 , i -> (i+1)%2 ).limit(6).distinct().forEach(System.out::println);
    }

    /**
     * 1,3,5,7,9,11
     * 找过流中大于2的元素然后将每个元素乘以2，然后忽略掉流中的前两个元素，
     * 然后在取流中的前两个元素，最后求出流中的元素综合
     * filter就是一个过滤,里面是predicate接口，满足条件就留下
     * mapToInt skip  limit  sum
     * distinct 是去重
     */
    @Test
    public void 作业(){
        Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(6);
        int reduce = stream.filter(item -> item > 2)
                .mapToInt(item -> item * 2)   //用mapToInt不用map，因为map最后拆箱的时候有性能损耗，jdk8极力避免这种损耗
                .skip(2) //忽略掉前面两个元素
                .limit(2) //取出前两个元素
                .sum(); //对所有的元素求和
                //这里用sum和reduce都可以，第一个参数并不是索引，而是第一个累加数
                //.reduce(0, Integer::sum);
        System.out.println(reduce);
    }

    /**
     * 求出流中最小或者最大的元素然后打印
     */
    @Test
    public void 作业1(){
        Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(6);
        stream.filter(item -> item > 2)
                .mapToInt(item -> item * 2)   //用mapToInt不用map，因为map最后拆箱的时候有性能损耗，jdk8极力避免这种损耗
                .skip(2) //忽略掉前面两个元素
                .limit(2) //取出前两个元素
                //取出最小的元素
                .min()
                //取出最大的元素
                //.max()
                .ifPresent(item -> System.out.println(item));
        //为什么min和max返回的是一个optional然而sum返回的是具体的值？
        //本质取决返回的这个值可不可能为空，因为对于sum如果里面为空sum得到的是一个0
    }

    /**
     *summaryStatistics是总结小计
     *
     */
    @Test
    public void summaryStatistics案例(){
        Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(6);
        IntSummaryStatistics summaryStatistics = stream.filter(item -> item > 2)
                .mapToInt(item -> item * 2)
                .skip(2)
                .limit(2)
                .summaryStatistics();
        summaryStatistics.getAverage();
        System.out.println(summaryStatistics.getCount());
        summaryStatistics.getMax();
        summaryStatistics.getMin();
        summaryStatistics.getSum();
    }

    /**
     *distinct去重
     */
    @Test
    public void distinct去重(){
        List<Integer> list = Arrays
                .asList(1, 2, 3, 4, 5, 6, 6, 6, 7, 7, 8, 8, 2, 2, 3, 3, 5, 5);
        ArrayList<Integer> collect1 = list.stream()
                .collect(Collectors.toCollection(ArrayList::new));

        HashSet<Integer> collect = list.stream()
                .collect(Collectors.toCollection(HashSet::new));

        System.out.println(collect1);
    }

    /**
     * 变成首字母大写
     *
     * //jdk8中使用stream有很多的调用链和调用方法，他的效率会受影响吗？
     * //在stream中循环只会循环一次可以理解为内部有一个容器，容器存放后面加的N个操作，这些操作都会存放在容器中
     * //一旦遇到及早求值或者终止操作的时候就会将容器中动作按照顺序执行，只会循环一次。
     */
    @Test
    public void ss(){
        List<String> list = Arrays.asList("hello","world","helloworld");
        //变成首字母大写
        list.stream()
                .map(item -> item.substring(0,1).toUpperCase()+item.substring(1))
                .forEach(System.out::println);
        //这个没有调用foreach去打印，而是放在每次转换完就会打印
        //这个不会打印出来，因为这个map是中间操作，都是惰性求值的，中间操作不会执行除非遇到了终止操作才可以执行。

        list.stream().map(item -> {
            String result = item.substring(0,1).toUpperCase()+item.substring(1);
            System.out.println(result);
            return result;
        });
    }


    @Test
    public void sorted排序(){
        System.out.println("111111111111111111111111");
        ArrayList<Integer> objects = new ArrayList<>();
        objects.add(5);
        objects.add(6);
        objects.add(7);
        objects.add(8);
        objects.add(9);
        objects.add(1);
        objects.add(2);
        objects.add(3);
        objects.add(4);
        objects.stream().sorted((a,b)-> {
            return a-b;
        }).collect(Collectors.toList()).forEach(System.out::println);
    }

}
